/***************************************************************************
                          iff.c  -  description
                          ---------------------
    copyright            : (C) 2005-07 Wolfgang Schulz
    email                : hagenx@gmx.de

    >>> Note: so far only 8Bit images loadable
 ***************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "math.h"

#include "iff.h"

/* read color map from raw mem-chunk into structure */
void read_cmap(FILE* fp, CMAP_chunk* cmap)
{
	char* c_cmap;
	unsigned int i;

	c_cmap = (char*)malloc((cmap->Size+1)*sizeof(char));
	cmap->cmap = (iff_p3*)malloc((cmap->Size/3)*sizeof(iff_p3));

	fread((char*)c_cmap,sizeof(char),cmap->Size,fp);
	for (i=0; i < (cmap->Size/3); i++)
	{
		cmap->cmap[i].p[0] = (unsigned char)c_cmap[(i*3)];
		cmap->cmap[i].p[1] = (unsigned char)c_cmap[((i*3)+1)];
		cmap->cmap[i].p[2] = (unsigned char)c_cmap[((i*3)+2)];
	}
	free(c_cmap);
}

/*
void read_camg()
{
}
*/

/* read image's body from raw mem-chunk into structure */
void read_body(iffimage* img,
			   BMHD_chunk* bmhd, CMAP_chunk* cmap, BODY_chunk* body)
{
	int x,y,nPlane;
	unsigned char* img_cmapping;		// intermediate color-mem

	// Body-Data, example:
	// -------------------
	// $ 5000 3000 0000 0000
	// $ A000 6000 0000 0000
	// $ 6000 C000 0000 0000
	// $ A000 0000 0000 0000
	//   \__/  one plane						=> row_bytes
	//   \_______/ all planes
	//   \_________________/ filled up planes	=> plane_bytes
	//

	unsigned int  row_bytes;			// Number of bytes for one row of x-Pixels (per plane)
	unsigned int  plane_bytes;			// Num. of bytes for one row ... (for all planes)

	unsigned int  byteIndex;			// current byte
	unsigned char fetchByte;
	unsigned int  bitIndex;				// current bit (according to x-Pixel)
	unsigned char fetchBit;

	int i;
	unsigned char read_unpacked;		// counter for unpacked bytes to read
	unsigned char read_packed;			// counter for packed bytes to read

	row_bytes = (int)ceil(bmhd->w/8.0);	// round up / padd
	if (row_bytes < 2) row_bytes = 2;	// padded, when smaller than 16 pixels

	plane_bytes = bmhd->nPlanes * row_bytes;
	if (plane_bytes < 8) plane_bytes = 8;

	img_cmapping = (unsigned char*)calloc((img->height*img->width), sizeof(unsigned char));

	byteIndex = 0;
	read_unpacked = 0;
	read_packed = 0;
	for (y=0; y < img->height; y++)
	{
		for (nPlane=0; nPlane<img->depth; nPlane++)
		{
			for (x=0; x<img->width; x++)
			{
				bitIndex = x%8;
				if (bmhd->compression == 0)
				{
					if (bitIndex == 0)
					{
						fetchByte = body->Data[byteIndex];
						byteIndex++;
					}
				}
				if (bmhd->compression == 1)
				{
					// simple RLE, control byte =
					//   0-127: unpacked,	==> read next x bytes literally
					//	   128: ignore + read next control byte
					// 129-255: packed,		==> read next byte (257-x) times
					if (bitIndex == 0)
					{
						if (read_unpacked > 0)
						{
							fetchByte = body->Data[byteIndex++];
							read_unpacked--;
						}
						else if (read_packed > 0)
						{
							read_packed--;
						}
						else if ((read_unpacked == 0) && (read_packed == 0))
						{
							fetchByte = body->Data[byteIndex];
							if (fetchByte == 128)	// no-op
							{
								byteIndex++;
								fetchByte = body->Data[byteIndex];
							}
							if (fetchByte <= 127)
							{
								read_unpacked = fetchByte+1;
								byteIndex++;					// now get color-value
								fetchByte = body->Data[byteIndex++];
								read_unpacked--;
							}
							else // if (fetchByte > 128)
							{
								read_packed = 257-fetchByte;	// recent was repetition value
								byteIndex++;					// now get color-value
								fetchByte = body->Data[byteIndex++];
								read_packed--;
							}
						}
					}
				}
				fetchBit = (fetchByte & (1<<(7-bitIndex))) >> (7-bitIndex);

				if (fetchBit)
				{
					img_cmapping[(y*img->width)+x] |= (1<<nPlane);
				}
			}
		}
	}

	// create memory for iff-image
	img->data = (iff_p3**)malloc(img->height * sizeof(iff_p3*));
	for (y=0; y<img->height; y++)
	{
		img->data[y] = (iff_p3*)calloc(img->width, sizeof(iff_p3));
	}
	// transpose colormap-image to iff-image
	for (i=0; i<(img->width*img->height); i++)
	{
		y = (int)(i/img->width);
		x = (i%img->width);
		if (cmap->Size > 0)
		{
			img->data[y][x].p[0] = cmap->cmap[img_cmapping[i]].p[0];
			img->data[y][x].p[1] = cmap->cmap[img_cmapping[i]].p[1];
			img->data[y][x].p[2] = cmap->cmap[img_cmapping[i]].p[2];
		}
	}

	free(img_cmapping);
}

iffimage loadiff(char* filename)
{
	FILE* fp;
	int fType = -1;

	unsigned char* uc_buf;

	iffimage img;

	BMHD_chunk* bmhd;
	CMAP_chunk* cmap;
	DPPS_chunk* dpps;
	CAMG_chunk* camg;
	BODY_chunk* body;

	fp = fopen(filename,"rb");
    if (!fp)
	{
        printf("error opening %s - exiting\n",filename);
        exit(EXIT_FAILURE);
    }
	printf("loaded file: %s\n", filename);

	// - - - - - - - - - - - - - - -
	// read all CHUNKS now
	//		>> Chunks can be nested!
	// - - - - - - - - - - - - - - -

	uc_buf = (unsigned char*)calloc(255, sizeof(unsigned char));

	// read outmost Chunk: "FORM", "LIST" or "CAT "
	fread((char*)uc_buf,sizeof(char),4,fp);
	if (strcmp((char*)uc_buf,"FORM") == 0) fType = 0;
	if (strcmp((char*)uc_buf,"LIST") == 0) fType = 1;
	if (strcmp((char*)uc_buf,"CAT ") == 0) fType = 2;
	if (fType < 0)
	{
		printf("Not an IFF file - exiting\n");
        exit(EXIT_FAILURE);
	}

	bmhd = (BMHD_chunk*)malloc(1*sizeof(BMHD_chunk));
	cmap = (CMAP_chunk*)malloc(1*sizeof(CMAP_chunk));	// create this anyway
	dpps = (DPPS_chunk*)malloc(1*sizeof(DPPS_chunk));	// create this anyway
	camg = (CAMG_chunk*)malloc(1*sizeof(CAMG_chunk));	// create this anyway
	body = (BODY_chunk*)malloc(1*sizeof(BODY_chunk));

	/* read size of Chunk-data */
	fread((char*)uc_buf,sizeof(char),4,fp);
	bmhd->Size = (uc_buf[0] << 24) + (uc_buf[1] << 16) + (uc_buf[2] << 8) + uc_buf[3];

	// IFF: chunk Header, e.g. "ILBM"
	fread((char*)uc_buf,sizeof(char),4,fp);

	// - - - - - - - - - -
	// look for "BMHD" tag
	// - - - - - - - - - -
	fread((char*)uc_buf,sizeof(char),4,fp);

	// get "BMHD" chunk length --> always 20
	fread((char*)uc_buf,sizeof(char),4,fp);

	fread((char*)uc_buf,sizeof(char),2,fp);
	bmhd->w = (uc_buf[0] << 8) + uc_buf[1];

	fread((char*)uc_buf,sizeof(char),2,fp);
	bmhd->h = (uc_buf[0] << 8) + uc_buf[1];

	fread((char*)uc_buf,sizeof(char),2,fp);
	bmhd->x = (uc_buf[0] << 8) + uc_buf[1];

	fread((char*)uc_buf,sizeof(char),2,fp);
	bmhd->y = (uc_buf[0] << 8) + uc_buf[1];

	bmhd->nPlanes = fgetc(fp);
	bmhd->masking = fgetc(fp);
	bmhd->compression = fgetc(fp);
	bmhd->pad1 = fgetc(fp);

	fread((char*)uc_buf,sizeof(char),2,fp);
	bmhd->trclr = (uc_buf[0] << 8) + uc_buf[1];

	bmhd->xAspect = fgetc(fp);
	bmhd->yAspect = fgetc(fp);

	fread((char*)uc_buf,sizeof(char),2,fp);
	fread((char*)uc_buf,sizeof(char),2,fp);

	printiff(bmhd);
	img.width = bmhd->w;
	img.height = bmhd->h;
	img.depth = bmhd->nPlanes;

	// - - - - - - - - - - - - - - - - - - - - - - - -
	// look for "CMAP" | "CRNG" | "CAMG" | "BODY" tag
	// - - - - - - - - - - - - - - - - - - - - - - - -
	fread((char*)uc_buf,sizeof(char),4,fp);

	cmap->Size = 0;
	if (strcmp((char*)uc_buf,"CMAP") == 0)
	{
		// get "CMAP" chunk length
		fread((char*)uc_buf,sizeof(char),4,fp);
		cmap->Size = (uc_buf[0] << 24) + (uc_buf[1] << 16) + (uc_buf[2] << 8) + uc_buf[3];
		read_cmap(fp, cmap);

		fread((char*)uc_buf,sizeof(char),4,fp);
	}
	if (strcmp((char*)uc_buf,"DPPS") == 0)	// Deluxe Paint specific
	{
		fread((char*)uc_buf,sizeof(char),4,fp);
		dpps->Size = (uc_buf[0] << 24) + (uc_buf[1] << 16) + (uc_buf[2] << 8) + uc_buf[3];
		fread(uc_buf,sizeof(char),dpps->Size,fp);
		fread((char*)uc_buf,sizeof(char),4,fp);
	}
	if (strcmp((char*)uc_buf,"CAMG") == 0)	// ...
	{
		fread((char*)uc_buf,sizeof(char),4,fp);
		camg->Size = (uc_buf[0] << 24) + (uc_buf[1] << 16) + (uc_buf[2] << 8) + uc_buf[3];
		fread(uc_buf,sizeof(char),camg->Size,fp);
		fread((char*)uc_buf,sizeof(char),4,fp);
	}
	// maybe there's any other chunk inbetween ==> clear that out!
	if (strcmp((char*)uc_buf,"BODY") == 0)
	{
		// get "BODY" chunk length
		fread((char*)uc_buf,sizeof(char),4,fp);
		body->Size = (uc_buf[0] << 24) + (uc_buf[1] << 16) + (uc_buf[2] << 8) + uc_buf[3];
		body->Data = (unsigned char*)malloc((body->Size+1)*sizeof(unsigned char*));
		fread((char*)body->Data,sizeof(char),body->Size,fp);
		read_body(&img, bmhd, cmap, body);
	}

	free(body->Data);
	free(body);
	free(camg);
	free(dpps);
	if (cmap->Size > 0) free(cmap->cmap);
	free(cmap);
	free(bmhd);
	free(uc_buf);
	fclose(fp);

	return img;
}

void printiff(BMHD_chunk* bmhd)
{
	printf("img Size=%i\n",bmhd->Size);
	printf("img compression=%i\n",bmhd->compression);
	printf("img Planes=%i\n",bmhd->nPlanes);

	printf("img w=%i\n",bmhd->w);
	printf("img h=%i\n",bmhd->h);
}

void freeiff(iffimage img)
{
	int i;
	for (i=0; i < img.height; i++){
		free(img.data[i]);
	}
	free(img.data);
}
